#!/bin/bash

# Copyright 2017 The Fuchsia Authors
#
# Use of this source code is governed by a MIT-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/MIT

# Runs a single instance of the early boot entropy collection test.
#
# This script assumes that Zircon has already been built to support entropy quality tests (i.e with
# the ENABLE_ENTROPY_COLLECTOR_TEST flag set at compile time). It handles starting Zircon either in
# qemu or via netboot, then it extracts the entropy file once the boot-time test completes. Usually
# you will call a "test driver" script that in turn calls this script, rather than calling this
# script directly.
#
# Where sensible, the option flags agree with scripts/run-zircon.  The -a, -m, and -s command line
# options are mandatory; the rest are optional. The output directory must also be passed on the
# command line, as a positional argument (i.e. without a '-?' type flag).
#
# This script saves the captured entropy in a file named 'entropy.#########.bin' (to facilitate
# running repeated tests with the same output directory). It also saves a metadata file, named
# 'entropy.#########.meta', recording certain test parameters. See the source below for more
# details.
#
# BUGS: netboot isn't supported yet, only qemu.

set -e -u
CDPATH=
ZIRCONDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"

function HELP {
    echo "$0 [options] output-dir/" >&2
    echo "options:" >&2
    echo "-a <arch>   : arm64 or x86" >&2
    echo "-c <word>   : add word to kernel cmdline" >&2
    echo "-l <len>    : entropy test length (bytes to collect)" >&2
    echo "-m <method> : netboot or qemu" >&2
    echo "-n <name>   : Zircon nodename to use" >&2
    echo "-o <dir>    : build directory" >&2
    echo "-s <source> : hwrng or jitterentropy" >&2
    exit 1
}

ARCH=
BUILDDIR=
CMDLINE=""
LEN=
METHOD=
NODENAME=
OUTDIR=
SRC=

while getopts "a:c:hl:m:n:o:s:" FLAG; do
    case "$FLAG" in
        a) ARCH="$OPTARG";;
        c) CMDLINE+="$OPTARG ";;
        h) HELP;;
        l) LEN="$OPTARG";;
        m) METHOD="$OPTARG";;
        n) NODENAME="$OPTARG";;
        o) BUILDDIR="$OPTARG";;
        s) SRC="$OPTARG";;
        \?)
            echo "unrecognized option" >&2
            HELP
            ;;
    esac
done
shift $((OPTIND-1))

if [[ "$#" -ne "1" ]]; then
    echo "missing output dir" >&2
    HELP
fi
OUTDIR="$1"
shift

# check required args
if [[ -z $ARCH ]]; then
    echo "must specify arch" >&2
    HELP
fi
if [[ -z $METHOD ]]; then
    echo "must specify method" >&2
    HELP
fi
if [[ -z $SRC ]]; then
    echo "must specify source" >&2
    HELP
fi
if [[ -z $OUTDIR ]]; then
    echo "must specify outdir" >&2
    HELP
fi

# handle entropy-test specific cmdline args
CMDLINE+="kernel.entropy-test.src=$SRC "
if [[ -n $LEN ]]; then CMDLINE+="kernel.entropy-test.len=$LEN "; fi

# find and read builddir
PROJECT="$ARCH"

if [[ -z "$BUILDDIR" ]]; then
    if [[ -n $PROJECT ]]; then
        BUILDDIR="$ZIRCONDIR/build-$PROJECT"
    else
        echo "could not autodetect builddir. use -o." >&2
        HELP
    fi
fi

if [[ ! -d $BUILDDIR || ! -x $BUILDDIR/tools/netcp ]]; then
    echo "bad builddir: $BUILDDIR" >&2
    HELP
fi

BUILDID="$(sed -n '/^#define\s\+BUILDID/{s/^[^"]*"//;s/".*$//;p;q}' "$BUILDDIR/config-buildid.h")"

# set a few other variables
if [[ -z $NODENAME ]]; then
    if [[ $METHOD = "qemu" ]]; then
        NODENAME="entropy-test-$(head -c16 /dev/urandom | xxd -p)";
    elif [[ $METHOD = "netboot" ]]; then
        echo "missing nodename - required for netboot" >&2
        HELP
    fi
fi
CMDLINE+="zircon.nodename=$NODENAME "
NUM="$(cd "$OUTDIR";
       find . \( -type f -name "entropy.*.bin" -print \) -o \( -type d ! -name . -prune \) |
       sed 's/^.*entropy\.0*//;s/\.bin$//;s/^$/0/;' | grep '^[0-9]\+$' |
       sort -nr | head -n 1)"
if [[ -z $NUM ]]; then NUM=-1; fi
NUM="$(printf "%09d" "$((NUM + 1))")"
BINFILE="$OUTDIR/entropy.$NUM.bin"
METAFILE="$OUTDIR/entropy.$NUM.meta"

# launch zircon
case "$METHOD" in
    qemu)
        "$ZIRCONDIR/scripts/run-zircon" -a "$ARCH" -c "$CMDLINE" -N -o "$BUILDDIR" </dev/null &
        ;;
    netboot)
        "$BUILDDIR/tools/bootserver" --tftp -n "$NODENAME" \
            "$BUILDDIR/zircon.bin" "$BUILDDIR/bootdata.bin" \
            -- "$CMDLINE" >/dev/null 2>/dev/null &
        BOOTSERVER_PID=$!
        ;;
    *)
        echo "unrecognized method: $METHOD" >&2
        HELP
        ;;
esac
echo "launched zircon with nodename $NODENAME" >&2

# wait for zircon to send us the entropy file
while true; do
    sleep 5;
    if "$BUILDDIR"/tools/netcp --nowait --timeout=1000 \
        "$NODENAME:/boot/kernel/debug/entropy.bin" "$BINFILE" &&
        [[ -z $LEN ||  $(wc -c <"$BINFILE") -eq $LEN ]]
    then
        echo "Finished test on nodename $NODENAME" >&2
        break
    fi
done

# write the meta file
{
    echo "entropy-test $METHOD"
    echo "date         $(date +"%Y-%m-%d %H:%M:%S %Z")"
    echo "buildid      $BUILDID"
    echo "arch         $ARCH"
    echo "source       $SRC"
    echo "len          $LEN"
    echo "cmdline      $CMDLINE"
} >$METAFILE

# reset the test device
case "$METHOD" in
    qemu)
        while "$BUILDDIR"/tools/netaddr --nowait --timeout=1000 \
                "$NODENAME" >/dev/null 2>/dev/null; do
            "$BUILDDIR"/tools/netruncmd --nowait --timeout=1000 "$NODENAME" dm shutdown
            sleep 2
        done
        ;;
    netboot)
        kill "$BOOTSERVER_PID"
        sleep 1 # make sure bootserver actually has time to shut down
        while "$BUILDDIR"/tools/netaddr --nowait --timeout=1000 \
                "$NODENAME" >/dev/null 2>/dev/null; do
            "$BUILDDIR"/tools/netruncmd --nowait --timeout=1000 "$NODENAME" dm reboot
            sleep 2
        done
        ;;
esac
